package edu.cg7;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.vecmath.Point3d;
import javax.vecmath.Point3i;
import javax.vecmath.TexCoord3f;
import javax.vecmath.Vector3d;

public class WavefrontObjectReader {
	private String file;
	
	public WavefrontObjectReader(String file) {
		this.file = file;
	}
	
	private class WavefrontFacet{
		
		private List<Integer> vertices;
		private List<Integer> normals;
		private List<Integer> texCoords;
		
		public WavefrontFacet(String token1, String token2, String token3) {
			vertices = new ArrayList<>();
			normals = new ArrayList<>();
			texCoords = new ArrayList<>();
			
			parseFacetInfo(token1, token2, token3);
		}
		
		public List<Integer> getVertices(){
			return vertices;
		}
		public List<Integer> getNormal(){
			return normals;
		}
		public List<Integer> getTexCoords(){
			return texCoords;
		}
		
		private void parseFacetInfo(String token1, String token2, String token3){
			String[] touple1 = token1.split("/");
			String[] touple2 = token2.split("/");
			String[] touple3 = token3.split("/");
			
			if(touple1.length >= 1) {
				vertices.add(Integer.parseInt(touple1[0]));
				vertices.add(Integer.parseInt(touple2[0]));
				vertices.add(Integer.parseInt(touple3[0]));
			}
			
			if(touple1.length >= 2) {
				texCoords.add(Integer.parseInt(touple1[1]));
				texCoords.add(Integer.parseInt(touple2[1]));
				texCoords.add(Integer.parseInt(touple3[1]));
			}
			
			if(touple1.length >= 3) {
				normals.add(Integer.parseInt(touple1[2]));
				normals.add(Integer.parseInt(touple2[2]));
				normals.add(Integer.parseInt(touple3[2]));
			}
		}

	}
	
	public ITriangleMesh parseObject() {
		ITriangleMesh mesh = new TriangleMesh();
		List<Point3d> vertices = new ArrayList<>();
		List<Vector3d> normals = new ArrayList<>();
		List<TexCoord3f> textureCoords = new ArrayList<>();
		List<WavefrontFacet> facets = new ArrayList<>();
		
		try {
			BufferedReader br = new BufferedReader(new FileReader(file));
			String line;
			while ((line = br.readLine()) != null) {
			   String[] token = line.split("\\s+");
			   switch(token[0]) {
			   		case "v":
			   			Point3d v = new Point3d(Double.parseDouble(token[1]),Double.parseDouble(token[2]),Double.parseDouble(token[3]));
			   			vertices.add(v);
			   			break;
			   		case "vn":
			   			Vector3d vn = new Vector3d(Double.parseDouble(token[1]),Double.parseDouble(token[2]),Double.parseDouble(token[3]));
			   			normals.add(vn);
			   			break;
			   		case "vt":
			   			TexCoord3f vt = new TexCoord3f(Float.parseFloat(token[1]),Float.parseFloat(token[2]), 0);
			   			textureCoords.add(vt);
			   			break;
			   		case "f":
			   			WavefrontFacet facet = new WavefrontFacet(token[1],token[2],token[3]);
			   			facets.add(facet);
			   			break;
			   		default:
			   			break;
			   }
			   
			}
			br.close();
			
			System.out.println("Wavefront File readed:");
			System.out.println("vertices: " + vertices.size());
			System.out.println("normals: " + normals.size());
			System.out.println("texture coordinates: " + textureCoords.size());
			System.out.println("facets: " + facets.size());
			
			for(Point3d vertex : vertices) {
				mesh.addVertex(vertex);
			}
			
			for(TexCoord3f texCoord : textureCoords) {
				mesh.addTextureCoordinate(texCoord);
			}
			
			Triangle triangle;
			List<Integer> tmpVertices;
			List<Integer> tmpTextureCoords;
			List<Integer> tmpNormal;
			for(WavefrontFacet facet : facets) {
				tmpVertices = facet.getVertices();
				tmpTextureCoords = facet.getTexCoords();
				tmpNormal = facet.getNormal();
				
				
				if(tmpTextureCoords.size() > 0) {
					triangle = new Triangle(tmpVertices.get(0)-1, tmpVertices.get(1)-1, tmpVertices.get(2)-1, 
							tmpTextureCoords.get(0)-1, tmpTextureCoords.get(1)-1, tmpTextureCoords.get(2)-1);
				}else{
					triangle = new Triangle(tmpVertices.get(0)-1, tmpVertices.get(1)-1, tmpVertices.get(2)-1);
				}
				
				if(!tmpNormal.isEmpty()) {
					triangle.setNormal1(normals.get(tmpNormal.get(0)-1));
					triangle.setNormal2(normals.get(tmpNormal.get(1)-1));
					triangle.setNormal3(normals.get(tmpNormal.get(2)-1));
				}else{
					triangle.computeNormal(vertices.get(tmpVertices.get(0)-1), 
											vertices.get(tmpVertices.get(1)-1), 
											vertices.get(tmpVertices.get(2)-1));
				}
				
				mesh.addTriangle(triangle);
			}
			
			if(normals.isEmpty()) {
				Point3d vertex;
				Triangle tmpTriangle;
				Map<Integer, Set<Triangle>> indexMap = new HashMap<Integer, Set<Triangle>>();
				for(int i = 0; i < mesh.getNumberOfVertices(); i++) {
					vertex = mesh.getVertex(i);
					
					indexMap.put(i, new HashSet<Triangle>());
					
					for(int t = 0; t < mesh.getNumberOfTriangles(); t++) {
						tmpTriangle = mesh.getTriangle(t);
						
						if(tmpTriangle.getVertex1() == i) {
							indexMap.get(i).add(tmpTriangle);
						}
						if(tmpTriangle.getVertex2() == i) {
							indexMap.get(i).add(tmpTriangle);
						}
						if(tmpTriangle.getVertex3() == i) {
							indexMap.get(i).add(tmpTriangle);
						}
					}
				}
				
				Vector3d sumVertex;
				Set<Triangle> triangleSet;
				for(int i = 0; i < mesh.getNumberOfVertices(); i++) {
					triangleSet = indexMap.get(i);
					
					sumVertex = new Vector3d();
					
					for(Triangle t : triangleSet) {
						sumVertex.add(t.getTriangleNormal());
					}
					
					sumVertex.scale(1.0/triangleSet.size());
					
					sumVertex.normalize();
					
					for(Triangle t : triangleSet) {
						if(t.getVertex1() == i) {
							t.setNormal1(new Vector3d(sumVertex));
						}

						if(t.getVertex2() == i) {
							t.setNormal2(new Vector3d(sumVertex));
						}
						
						if(t.getVertex3() == i) {
							t.setNormal3(new Vector3d(sumVertex));
						}
					}
				}
			}
			
		}catch(FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		return mesh;
	}

	public static void main(String[] args) {
		WavefrontObjectReader waveReader = new WavefrontObjectReader("cube.obj");
		waveReader.parseObject();
//		int[] i = new int[3];
//		System.out.println(Arrays.toString(i));
	}
}
